package main

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"gopkg.in/fatih/set.v0"
	"log"
	"net/http"
	"strconv"
	"sync"
)

type Message struct {
	FormId   string `json:"userID1"`
	TargetId string `json:"userID2"`
	Content  string `json:"data"`
}

type Node struct {
	Conn      *websocket.Conn
	DataQueue chan []byte //DataQueue 用于存储待处理的数据，从 WebSocket 连接接收到的消息数据。
	//set.Interface 是一个集合类型的接口，可以用于实现不同类型的集合，例如包含不重复元素的集合，比如后续可以将多个用户分到同一个组
	//然后可以对这个一个组内的消息进行遍历然后群发消息。可以理解为并发安全的集合。
	GroupSets set.Interface
}

// 映射关系  通过一个map集合来存储多个用户和节点的映射关系，因为每个用户和服务端建立的websocket连接地址都不同
// 比如ws://localhost:8088/ws?userID1和ws://localhost:8088/ws?userID2等等。。。
var clientMap map[int64]*Node = make(map[int64]*Node, 0)

// 读写锁  多个用户在建立连接的时候都要存储到map中，所以这里用一个锁保证读写的安全
var rwLocker sync.RWMutex

func main() {
	http.HandleFunc("/ws", Chat)
	log.Fatal(http.ListenAndServe(":8088", nil))
}

// Chat 用来接收用户发起ws连接，以及后续用协程来持续读取消息，并推送给其他用户
func Chat(writer http.ResponseWriter, request *http.Request) {
	query := request.URL.Query()
	Id := query.Get("userID")
	//log.Println("接收到的 userID:", Id)
	userId, _ := strconv.ParseInt(Id, 10, 64)

	isvalida := true
	conn, err := (&websocket.Upgrader{ //将 HTTP 连接升级为 WebSocket 连接
		//CheckOrigin 函数用于检查请求的来源是否合法,即允许跨域访问
		CheckOrigin: func(r *http.Request) bool {
			return isvalida
		},
	}).Upgrade(writer, request, nil) //request 参数包含了客户端发起连接时提供的 URL 信息ws://localhost:8088/userid

	if err != nil {
		log.Println("升级为WebSocket连接失败:", err)
		return
	}
	//2.获取conn
	node := &Node{
		Conn:      conn,
		DataQueue: make(chan []byte, 50),
		GroupSets: set.New(set.ThreadSafe),
	}
	defer conn.Close()

	//4. userid 跟 node绑定 并加锁  用程序的读写锁抢占线程资源
	rwLocker.Lock()
	clientMap[userId] = node
	rwLocker.Unlock()

	//5.用协程完成发送消息推送逻辑
	go sendProc(node)
	//6.用协程完成发送消息逻辑
	go recvProc(node)
	//这里有点小问题其实，然后用一个select 来阻塞主handle，然后等待协程中的发送消息以及消息推送
	select {}
}

// 5.完成发送逻辑   发送的是消息推送
func sendProc(node *Node) {
	for {
		select {
		//当有人给自己发送了数据后，那么自己的node.DataQueue中就会有数据
		//然后就可以通过webScoket把自己接收到数据的事情传递给前端。也就是消息推送
		case data := <-node.DataQueue: //这个同样是阻塞式操作
			//告诉前端有人私聊自己了，这个消息推送通过websocket发送出去
			err := node.Conn.WriteMessage(websocket.TextMessage, data)
			if err != nil {
				fmt.Println(err)
				return
			}
		}
	}
}

// 6.完成发送消息逻辑
func recvProc(node *Node) {
	for {
		//发送操作是前端操作的，即前端将用户A发送的消息内容放置到的websocket中
		//后端接收数据的data中就包含内容是谁给谁发送的信息。后续可以将这部分数据拆分出来后然后保存到数据库，
		//使用的是Conn接收的数据，发送的请求，而不是http了
		_, data, err := node.Conn.ReadMessage()
		if err != nil {
			fmt.Println("推送消息失败:", err)
			break
		}
		//用户A发送的消息给解析展示一下
		var myData Message
		err = json.Unmarshal(data, &myData)
		if err != nil {
			log.Println("解析JSON失败:", err)
			break
		}
		fmt.Println("[ws]发送消息:", myData)
		//用户A发送给别人的消息，要将别人的node.DataQueue中放入消息，然后别人通过sendProc来发送给前端
		broadMsg(data)
	}
}

func broadMsg(data []byte) {
	msg := Message{}
	err := json.Unmarshal(data, &msg)
	if err != nil {
		fmt.Println(err)
		return
	}
	str, err := strconv.ParseInt(msg.TargetId, 10, 64)
	if err != nil {
		fmt.Println(err)
		return
	}
	//将用户A发送给别人（msg.TargetId）的消息放入到TargetId对应的node.DataQueue中
	sendMsg(str, data)
	//后续的扩展暂时不管群发，广播，广播
	//switch msg.Type {
	//case strconv.Itoa(1): //私信
	//	sendMsg(int64(msg.TargetId), data)
	// case 2:  //群发
	// 	sendGroupMsg()
	// case 3://广播
	// 	sendAllMsg()
	//case 4:
	//
	//}
}

func sendMsg(TargetId int64, msg []byte) {
	rwLocker.RLock()
	//通过接收消息的用户id来找到目标用户的node
	node, ok := clientMap[TargetId] //因为这个方法是接收数据方法下沉过来的，所以接收对象的目的对象就是UserId   所以多个人建立多个对象，也就是都相符了。
	rwLocker.RUnlock()
	if ok {
		//在这个目标用户的node的DataQueue中放入数据，这样如果对方是在线状态的话，它的sendProc函数中就会收到消息，然后推送给前端
		node.DataQueue <- msg
	}
}
